package com.zhengqiangblog.core;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;

public class MappingHelper implements InitializingBean {

	private static final Logger logger = Logger.getLogger(MappingHelper.class);

	private Map<String, ActionMapping> mappings = new HashMap<String, ActionMapping>();
	private Map<String, ActionMapping> dynamicMappings = new HashMap<String, ActionMapping>();

	private MappingConfigs mappingConfigs = ApplicationContext.get(SimpleMappingConfigs.class);

	private static final String REGEX_PATH_VARIABLES_NO_BRACES = "\\{(\\w+)\\}";
	private static final String REGEX_PATH_VARIABLES_WITH_BRACES = "(\\{\\w+\\})";
	private static final String REGEX_PATH_VARIABLE_PATTERN = "(\\\\w+)";

	public static final String SLASH = "/";

	public MappingHelper() {
		logger.info("MappingHelper initializing...");
	}

	@Override
	public void init() {
		logger.info("MappingHelper's init method has been called...");
		Map<String, NamespaceMapping> namespaces = mappingConfigs.configure();
		this.buildMappings(namespaces);
		this.debug();
	}

	private void buildMappings(Map<String, NamespaceMapping> namespaces) {
		Iterator<Entry<String, NamespaceMapping>> nsIterator = namespaces.entrySet().iterator();
		while (nsIterator.hasNext()) {
			Entry<String, NamespaceMapping> nsEntry = nsIterator.next();
			String nsPath = nsEntry.getKey();
			NamespaceMapping namespace = nsEntry.getValue();
			Iterator<Entry<String, ActionMapping>> actionIterator = namespace.getActions().entrySet().iterator();
			while (actionIterator.hasNext()) {
				Entry<String, ActionMapping> actionEntry = actionIterator.next();
				String actionPath = actionEntry.getKey();
				boolean isDynamicActionPath = this.isDynamicActionPath(actionPath);
				if (isDynamicActionPath) {
					List<String> pathVariableNames = this.getPathVariableNames(actionPath);
					String regexActionPath = this.convertToRegexActionPath(actionPath);
					ActionMapping action = actionEntry.getValue();
					RequestMethod method = action.getMethod();
					StringBuffer sbPath = new StringBuffer();
					if (!nsPath.equals(SLASH)) {
						sbPath.append(nsPath);
					}
					sbPath.append(regexActionPath);
					sbPath.append("_");
					sbPath.append(method);
					String path = sbPath.toString();
					action.setPathVariableNames(pathVariableNames);
					dynamicMappings.put(path, action);
				} else {
					ActionMapping action = actionEntry.getValue();
					RequestMethod method = action.getMethod();
					StringBuffer sbPath = new StringBuffer();
					if (!nsPath.equals(SLASH)) {
						sbPath.append(nsPath);
					}
					sbPath.append(actionPath);
					sbPath.append("_");
					sbPath.append(method);
					String path = sbPath.toString();
					mappings.put(path, action);
				}
			}
		}
	}

	public ActionMapping getActionMapping(String path, RequestMethod requestMethod, Model model) {
		ActionMapping mapping = null;
		String pathWithMethod = path + "_" + requestMethod;
		String pathWithAllMethod = path + "_" + RequestMethod.ALL;
		mapping = this.getActionMappingInMappings(pathWithMethod);
		if (mapping == null) {
			mapping = this.getActionMappingInMappings(pathWithAllMethod);
		}
		if (mapping == null) {
			mapping = this.getActionMappingInDynamicMappings(pathWithMethod, model);
		}
		if (mapping == null) {
			mapping = this.getActionMappingInDynamicMappings(pathWithAllMethod, model);
		}
		return mapping;
	}

	private ActionMapping getActionMappingInMappings(String pathWithMethod) {
		return mappings.get(pathWithMethod);
	}

	private ActionMapping getActionMappingInDynamicMappings(String pathWithMethod, Model model) {
		ActionMapping mapping = null;
		Iterator<Entry<String, ActionMapping>> iterator = dynamicMappings.entrySet().iterator();
		while (iterator.hasNext()) {
			Entry<String, ActionMapping> entry = iterator.next();
			String regexPath = entry.getKey();
			boolean matches = this.isMatchDynamicActionPath(regexPath, pathWithMethod);
			if (matches) {
				mapping = entry.getValue();
				List<String> pathVariableValues = this.getPathVariableValues(regexPath, pathWithMethod);
				List<String> pathVariableNames = mapping.getPathVariableNames();
				for (int i = 0; i < pathVariableNames.size(); i++) {
					String name = pathVariableNames.get(i);
					String value = pathVariableValues.get(i);
					model.addPathVariable(name, value);
				}
			}
		}
		return mapping;
	}

	private boolean isDynamicActionPath(String actionPath) {
		Pattern pattern = Pattern.compile(REGEX_PATH_VARIABLES_NO_BRACES);
		Matcher matcher = pattern.matcher(actionPath);
		return matcher.find();
	}

	private boolean isMatchDynamicActionPath(String regexPath, String path) {
		Pattern pattern = Pattern.compile(regexPath);
		Matcher matcher = pattern.matcher(path);
		return matcher.matches();
	}

	private String convertToRegexActionPath(String actionPath) {
		return actionPath.replaceAll(REGEX_PATH_VARIABLES_WITH_BRACES, REGEX_PATH_VARIABLE_PATTERN);
	}

	private List<String> getPathVariableNames(String actionPath) {
		List<String> variableNames = new ArrayList<String>();
		Pattern pattern = Pattern.compile(REGEX_PATH_VARIABLES_NO_BRACES);
		Matcher matcher = pattern.matcher(actionPath);
		while (matcher.find()) {
			String variableName = matcher.group(1);
			variableNames.add(variableName);
		}
		return variableNames;
	}

	private List<String> getPathVariableValues(String regexPath, String path) {
		List<String> variableValues = new ArrayList<String>();
		Pattern pattern = Pattern.compile(regexPath);
		Matcher matcher = pattern.matcher(path);
		if (matcher.find()) {
			int groupCount = matcher.groupCount();
			for (int i = 1; i <= groupCount; i++) {
				String variableValue = matcher.group(i);
				variableValues.add(variableValue);
			}
		}
		return variableValues;
	}

	public static boolean isSlash(String str) {
		return SLASH.equals(str);
	}

	public static boolean isStartWithSlash(String str) {
		return StringUtils.startsWith(str, SLASH);
	}

	public static boolean isEndWithSlash(String str) {
		return StringUtils.endsWith(str, SLASH);
	}

	private void debug() {
		logger.info("====== STATIC MAPPINGS ======");
		this.showDebug(this.mappings);
		logger.info("====== DYNAMIC MAPPINGS ======");
		this.showDebug(this.dynamicMappings);
	}

	private void showDebug(Map<String, ActionMapping> mappings) {
		Iterator<Entry<String, ActionMapping>> iterator = mappings.entrySet().iterator();
		while (iterator.hasNext()) {
			Entry<String, ActionMapping> entry = iterator.next();
			String path = entry.getKey();
			ActionMapping mapping = entry.getValue();
			String action = mapping.getAction().getClass().getName();
			String actionMethod = mapping.getActionMethod();
			String actionForm = null;
			if (mapping.getActionForm() != null) {
				actionForm = mapping.getActionForm().getClass().getName();
			}
			List<String> pathVariableNames = mapping.getPathVariableNames();
			Map<String, Result> results = mapping.getResults();
			String strPathVariableNames = "";
			String strResults = "";
			if (pathVariableNames != null && pathVariableNames.size() > 0) {
				strPathVariableNames += pathVariableNames.get(0);
				for (int i = 1; i < pathVariableNames.size(); i++) {
					strPathVariableNames += ", " + pathVariableNames.get(i);
				}
			}
			if (results != null && results.size() > 0) {
				Iterator<Entry<String, Result>> resultsIterator = results.entrySet().iterator();
				while (resultsIterator.hasNext()) {
					Entry<String, Result> resultEntry = resultsIterator.next();
					String resultName = resultEntry.getKey();
					Result resultValue = resultEntry.getValue();
					strResults += "\t\t" + resultName + ": ";
					strResults += resultValue.getResultType().toString() + " ";
					strResults += resultValue.getValue();
					strResults += System.lineSeparator();
				}
			}
			String infos = String.format(
					"Path: %s%n\tAction: %s%n\tAction Method: %s%n\tAction Form: %s%n\tPath Variables: %s%n\tResults: %n%s", path, action,
					actionMethod, actionForm, strPathVariableNames, strResults);
			logger.info(infos);
		}
	}

}
